﻿using System;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Threading;
using System.Windows.Input;
using System.Windows.Shapes;

namespace UpDownPhone
{
    [TemplateVisualState(Name = NormalState, GroupName = CommonStates)]
    [TemplateVisualState(Name = DisabledState, GroupName = CommonStates)]
    public class UpDownControl : Control
    {

        public event EventHandler<UpDownCustomValueChangedEvent> UpDownValueChanged;

        public UpDownControl()
        {
            DefaultStyleKey = typeof(UpDownControl);

            _defaultArrowColor = new SolidColorBrush((Color)Application.Current.Resources["PhoneAccentColor"]);

            _defaultArrowDisabledColor = new SolidColorBrush((Color)Application.Current.Resources["PhoneDisabledColor"]);

            Loaded += UpDownControlLoaded;
        }

        private readonly SolidColorBrush _defaultArrowColor;
        private readonly SolidColorBrush _defaultArrowDisabledColor;

        private DispatcherTimer _timer;
        private Button _bntLeft;
        private Button _bntright;
        private Path _arrLeft;
        private Path _arrright;
        private TextBlock _textValue;
        private TextBlock _textAfter;

        private int _countTick;
        private bool _leftHold;
        private bool _rightHold;

        private const string DownArrow = "M0,0 L1,0 0.5,1Z";
        private const string UpArrow = "M0,1 L1,1 0.5,0Z";
        private const string RightArrow = "M0,0 L1,0.5 0,1Z";
        private const string LeftArrow = "M0,0.5 L1,1 1,0Z";


        private void UpDownControlLoaded(object sender, RoutedEventArgs e)
        {
            if (MinValue > MaxValue)
            {
                MinValue = MaxValue;
            }
            if (MaxValue < MinValue)
            {
                MaxValue = MinValue;
            }
            if (Value > MaxValue)
            {
                SetValue(ValueProperty, MaxValue);
            }
            if (Value < MinValue)
            {
                SetValue(ValueProperty, MinValue);
            }

            _timer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(SpeedHold) };
            _timer.Tick += TimerTick;

            _textValue = GetTemplateChild("TextValue") as TextBlock;
            _textAfter = GetTemplateChild("TextInfo") as TextBlock;

            _arrright = GetTemplateChild("ArrowRight") as Path;
            _arrLeft = GetTemplateChild("ArrowLeft") as Path;

            _bntLeft = GetTemplateChild("BntLeft") as Button;
            _bntright = GetTemplateChild("BntRight") as Button;

            if (_textValue == null)
            {
                throw new InvalidCastException("TextValue element not found");
            }
            if (_textAfter == null)
            {
                throw new InvalidCastException("TextInfo element not found");
            }
            if (_arrright == null)
            {
                throw new InvalidCastException("ArrowRight element not found");
            }
            if (_arrLeft == null)
            {
                throw new InvalidCastException("ArrowLeft element not found");
            }
            if (_bntLeft == null)
            {
                throw new InvalidCastException("BntLeft element not found");
            }
            if (_bntright == null)
            {
                throw new InvalidCastException("BntRight element not found");
            }
            
            
            _bntLeft.Click += BntLeftClick;
            _bntLeft.Hold += BntLeftHold;

            _bntright.Click += BntRightClick;
            _bntright.Hold += BntRightHold;

            if (ArrowColor == null)
            {
                ArrowColor = _defaultArrowColor;
            }

            if (ArrowDisabledColor == null)
            {
                ArrowDisabledColor = _defaultArrowDisabledColor;
            }

            _arrLeft.Fill = ArrowColor;
            _arrright.Fill = ArrowColor;

            switch (ArrowDirection)
            {
                case UpDownArrowTypes.LeftRight:
                    {
                        _arrLeft.Data = new StringToPathGeometryConverter().Convert(LeftArrow);
                        _arrright.Data = new StringToPathGeometryConverter().Convert(RightArrow);
                    }
                    break;
                case UpDownArrowTypes.UpDown:
                    {
                        _arrLeft.Data = new StringToPathGeometryConverter().Convert(DownArrow);
                        _arrright.Data = new StringToPathGeometryConverter().Convert(UpArrow);
                    }
                    break;
                case UpDownArrowTypes.External:
                    {
                        if (string.IsNullOrEmpty(ExternalPathLeft))
                        {
                            throw new InvalidOperationException("ExternalPathLeft is null or empty");
                        }
                        if (string.IsNullOrEmpty(ExternalPathRight))
                        {
                            throw new InvalidOperationException("ExternalPathRight is null or empty");
                        }
                        _arrLeft.Data = new StringToPathGeometryConverter().Convert(ExternalPathLeft);
                        _arrright.Data = new StringToPathGeometryConverter().Convert(ExternalPathRight);
                    }
                    break;
            }

            CompositionTarget.Rendering += DoWorkOnRender;
        }

        private void DoWorkOnRender(object sender, EventArgs args)
        {
            CompositionTarget.Rendering -= DoWorkOnRender;
            if (Value >= MaxValue)
            {
                _arrright.Fill = _defaultArrowDisabledColor;
                _bntright.IsEnabled = false;
            }
            else
            {
                _arrright.Fill = ArrowColor;
                _bntright.IsEnabled = true;
            }
            if (Value <= MinValue)
            {
                _arrLeft.Fill = _defaultArrowDisabledColor;
                _bntLeft.IsEnabled = false;
            }
            else
            {
                _arrLeft.Fill = ArrowColor;
                _bntLeft.IsEnabled = true;
            }
            _textValue.Text = Value.ToString(CultureInfo.InvariantCulture);
            OnChangedValue(Value, Value, Value.ToString(CultureInfo.InvariantCulture), TextAfterValue);
        }

        private void OnChangedValue(double oldvalue, double newvalue, string customvalue, string aftertext)
        {
            if (UpDownValueChanged != null)
            {
                var tmp = UpDownValueChanged;
                var arg = new UpDownCustomValueChangedEvent(oldvalue, newvalue, customvalue, aftertext);
                tmp(this, arg);
                Value = arg.NewValue;
                _textValue.Text = arg.CustomValue;
                _textAfter.Text = arg.CustomText;
            }
            else
            {
                _textValue.Text = newvalue.ToString(CultureInfo.InvariantCulture);
                _textAfter.Text = aftertext;
            }
        }

        private void TimerTick(object sender, EventArgs e)
        {
            _countTick++;
            double inc = Increment;
            if (_leftHold)
            {
                if (_countTick > IncrementHoldStart)
                {
                    inc = IncrementHold;
                }
                double value = Value;
                value -= inc;
                if (value <= MinValue)
                {
                    value = MinValue;
                    _leftHold = false;
                    _countTick = 0;
                    _timer.Stop();
                }
                Value = value;
            }
            else if (_rightHold)
            {
                if (_countTick > IncrementHoldStart)
                {
                    inc = IncrementHold;
                }
                double value = Value;
                value += inc;
                if (value > MaxValue)
                {
                    value = MaxValue;
                    _rightHold = false;
                    _countTick = 0;
                    _timer.Stop();
                }
                Value = value;
            }
        }

        private void BntLeftClick(object sender, RoutedEventArgs e)
        {
            if (_timer.IsEnabled)
            {
                _countTick = 0;
                _timer.Stop();
                return;
            }

            double value = Value;
            value -= Increment;
            if (value <= MinValue)
            {
                value = MinValue;
            }
            Value = value;

        }

        private void BntRightClick(object sender, RoutedEventArgs e)
        {
            if (_timer.IsEnabled)
            {
                _countTick = 0;
                _timer.Stop();
                return;
            }
            double value = Value;
            value += Increment;
            if (value >= MaxValue)
            {
                value = MaxValue;
            }
            Value = value;
        }

        private void BntLeftHold(object sender, GestureEventArgs e)
        {
            _timer.Stop();
            _leftHold = true;
            _rightHold = false;
            _countTick = 0;
            _timer.Start();
        }

        private void BntRightHold(object sender, GestureEventArgs e)
        {
            _timer.Stop();
            _leftHold = false;
            _rightHold = true;
            _countTick = 0;
            _timer.Start();
        }

        private const string CommonStates = "CommonStates";

        private const string NormalState = "Normal";

        private const string DisabledState = "Disabled";

        public static readonly DependencyProperty HeaderProperty =
            DependencyProperty.Register("Header", typeof(object), typeof(UpDownControl), new PropertyMetadata(null));

        public object Header
        {
            get { return GetValue(HeaderProperty); }
            set { SetValue(HeaderProperty, value); }
        }

        public static readonly DependencyProperty HeaderTemplateProperty =
            DependencyProperty.Register("HeaderTemplate", typeof(DataTemplate), typeof(UpDownControl), new PropertyMetadata(null));

        public DataTemplate HeaderTemplate
        {
            get { return (DataTemplate)GetValue(HeaderTemplateProperty); }
            set { SetValue(HeaderTemplateProperty, value); }
        }

        public static readonly DependencyProperty ArrowColorProperty =
            DependencyProperty.Register("ArrowColor", typeof(Brush), typeof(UpDownControl), null);

        public Brush ArrowColor
        {
            get { return (Brush)GetValue(ArrowColorProperty); }
            set { SetValue(ArrowColorProperty, value); }
        }

        public static readonly DependencyProperty ArrowDirectionProperty =
            DependencyProperty.Register("ArrowDirection", typeof(UpDownArrowTypes), typeof(UpDownControl),
                new PropertyMetadata(UpDownArrowTypes.LeftRight, null));

        public UpDownArrowTypes ArrowDirection
        {
            get { return (UpDownArrowTypes)GetValue(ArrowDirectionProperty); }
            set { SetValue(ArrowDirectionProperty, value); }
        }

        public static readonly DependencyProperty ArrowDisabledColorProperty =
            DependencyProperty.Register("ArrowDisabledColor", typeof(Brush), typeof(UpDownControl), null);

        public Brush ArrowDisabledColor
        {
            get { return (Brush)GetValue(ArrowDisabledColorProperty); }
            set { SetValue(ArrowDisabledColorProperty, value); }
        }

        public static readonly DependencyProperty GapArrowProperty =
                DependencyProperty.Register("GapArrow", typeof(double), typeof(UpDownControl),
                                new PropertyMetadata(double.Parse("30"), null));

        public double GapArrow
        {
            get { return (double)GetValue(GapArrowProperty); }
            set { SetValue(GapArrowProperty, value); }
        }

        public static readonly DependencyProperty MinValueProperty =
            DependencyProperty.Register("MinValue", typeof(double), typeof(UpDownControl),
                                new PropertyMetadata(double.Parse("0"), null));

        public double MinValue
        {
            get { return (double)GetValue(MinValueProperty); }
            set { SetValue(MinValueProperty, value); }
        }

        public static readonly DependencyProperty MaxValueProperty =
            DependencyProperty.Register("MaxValue", typeof(double), typeof(UpDownControl),
                                        new PropertyMetadata(double.Parse("0"), null));

        public double MaxValue
        {
            get { return (double)GetValue(MaxValueProperty); }
            set { SetValue(MaxValueProperty, value); }
        }

        public static readonly DependencyProperty SpeedHoldProperty =
            DependencyProperty.Register("SpeedHold", typeof(int), typeof(UpDownControl),
                                new PropertyMetadata(int.Parse("250"), null));

        public int SpeedHold
        {
            get { return (int)GetValue(SpeedHoldProperty); }
            set
            {
                if (value < 100)
                {
                    throw new ArgumentOutOfRangeException("value", "SpeedHold value must be between 100 and 500");
                }
                if (value > 500)
                {
                    throw new ArgumentOutOfRangeException("value", "SpeedHold value must be between 100 and 500");
                }
                SetValue(SpeedHoldProperty, value);
            }
        }

        public static readonly DependencyProperty ExternalPathLeftProperty =
            DependencyProperty.Register("ExternalPathLeft", typeof(string), typeof(UpDownControl),
                                new PropertyMetadata(default(string), null));

        public string ExternalPathLeft
        {
            get { return (string)GetValue(ExternalPathLeftProperty); }
            set { SetValue(ExternalPathLeftProperty, value); }
        }

        public static readonly DependencyProperty ExternalPathRightProperty =
            DependencyProperty.Register("ExternalPathRight", typeof(string), typeof(UpDownControl),
                                new PropertyMetadata(default(string), null));

        public string ExternalPathRight
        {
            get { return (string)GetValue(ExternalPathRightProperty); }
            set { SetValue(ExternalPathRightProperty, value); }
        }

        public static readonly DependencyProperty IncrementHoldStartProperty =
            DependencyProperty.Register("IncrementHoldStart", typeof(int), typeof(UpDownControl),
                                new PropertyMetadata(int.Parse("5"), null));

        public int IncrementHoldStart
        {
            get { return (int)GetValue(IncrementHoldStartProperty); }
            set { SetValue(IncrementHoldStartProperty, value); }
        }

        public static readonly DependencyProperty IncrementHoldProperty =
            DependencyProperty.Register("IncrementHold", typeof(double), typeof(UpDownControl),
                                        new PropertyMetadata(double.Parse("0"), null));

        public double IncrementHold
        {
            get { return (double)GetValue(IncrementHoldProperty); }
            set { SetValue(IncrementHoldProperty, value); }
        }

        public static readonly DependencyProperty IncrementProperty =
            DependencyProperty.Register("Increment", typeof(double), typeof(UpDownControl),
                                        new PropertyMetadata(double.Parse("0"), null));

        public double Increment
        {
            get { return (double)GetValue(IncrementProperty); }
            set { SetValue(IncrementProperty, value); }
        }

        public static readonly DependencyProperty ValueProperty =
            DependencyProperty.Register("Value", typeof(double), typeof(UpDownControl),
                                new PropertyMetadata(double.Parse("0"), null));

        public double Value
        {
            get { return (double) GetValue(ValueProperty); }
            set
            {
                var oldvalue = (double)GetValue(ValueProperty);
                if (!oldvalue.Equals(value))
                {
                    if (value >= MaxValue)
                    {
                        value = MaxValue;
                        if (_arrright != null && _bntright != null)
                        {
                            _arrright.Fill = _defaultArrowDisabledColor;
                            _bntright.IsEnabled = false;
                        }
                    }
                    else
                    {
                        if (_arrright != null && _bntright != null)
                        {
                            _arrright.Fill = ArrowColor;
                            _bntright.IsEnabled = true;
                        }
                    }
                    if (value <= MinValue)
                    {
                        value = MinValue;
                        if (_arrLeft != null && _bntLeft != null)
                        {
                            _arrLeft.Fill = _defaultArrowDisabledColor;
                            _bntLeft.IsEnabled = false;
                        }
                    }
                    else
                    {
                        if (_arrLeft != null && _bntLeft != null)
                        {
                            _arrLeft.Fill = ArrowColor;
                            _bntLeft.IsEnabled = true;
                        }
                    }
                    SetValue(ValueProperty, value);
                    if (_textValue != null && _textAfter != null)
                    {
                        OnChangedValue(oldvalue, value, _textValue.Text, _textAfter.Text);
                    }
                }
            }
        }

        public static readonly DependencyProperty TextAfterValueProperty =
            DependencyProperty.Register("TextAfterValue", typeof(string), typeof(UpDownControl),
                                new PropertyMetadata(default(string), null));

        public string TextAfterValue
        {
            get { return (string)GetValue(TextAfterValueProperty); }
            set { SetValue(TextAfterValueProperty, value); }
        }

        private void ChangeVisualState(bool useTransitions)
        {
            VisualStateManager.GoToState(this, IsEnabled ? NormalState : DisabledState, useTransitions);
        }

        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
            IsEnabledChanged += delegate
            {
                ChangeVisualState(true);
            };
            ChangeVisualState(false);
        }

    }
}